/* Definitions for futexes (linux version).

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#include <linux/futex.h>
#include <syscall.h>
#include <unistd.h>
#include <climits>
#include <ctime>
#include <cerrno>

KP_DECLS_BEGIN

#ifndef FUTEX_PRIVATE_FLAG
#  define FUTEX_PRIVATE_FLAG   0
#endif

/* A futex accepts an int as its argument, but 'atomic_t' is word-sized.
 * For 64 bit platforms, this means that we may need to adjust the pointer. */

#if !defined (KP_ARCH_WIDE) || defined (KP_LITTLE_ENDIAN)
#  define FUTEX_PTR(ptr)   ptr
#else
#  define FUTEX_PTR(ptr)   ((int *)(ptr) + 1)
#endif

static int
futex_wait_impl (interpreter *, atomic_t *ptr, int value, double *tstamp)
{
  timespec ts, *tsp = nullptr;

  if (tstamp)
    {
      ts.tv_sec = (long)*tstamp;
      ts.tv_nsec = (long)((*tstamp - ts.tv_sec) * 1e9);
      tsp = &ts;
    }

  errno = 0;
  syscall (SYS_futex, FUTEX_PTR (ptr),
    FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME,
    tsp, nullptr, FUTEX_BITSET_MATCH_ANY);

  return (errno == ETIMEDOUT ? THR_TIMEOUT :
    errno == EINTR ? THR_INTR : 0);
}

static void
futex_wake (atomic_t *ptr, bool wake_all)
{
  syscall (SYS_futex, FUTEX_PTR (ptr), FUTEX_WAKE | FUTEX_PRIVATE_FLAG,
    wake_all ? INT_MAX : 1);
}

static void
futex_move (atomic_t *src, atomic_t *dst, bool wake_all)
{
  syscall (SYS_futex, FUTEX_PTR (src), FUTEX_REQUEUE | FUTEX_PRIVATE_FLAG,
    1, wake_all ? INT_MAX : 0, FUTEX_PTR (dst));
}

#define FUTEX_MOVE

static bool
futex_init (interpreter *)
{
  return (true);
}

KP_DECLS_END
